//
//  BBMeshView.m
//  BBTouch
//
//  Created by ben smith on 8/14/08.
//  This file is part of BBTouch.
//
//  BBTouch is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.

//  BBTouch is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.

//  You should have received a copy of the GNU Lesser General Public License
//  along with BBTouch.  If not, see <http://www.gnu.org/licenses/>.
// 
//  Copyright 2008 Ben Britten Smith ben@benbritten.com .
//

#import "BBMeshView.h"
#import "BBDistortionMesh.h"
#import "BBIndexedVertex.h"
#import "BBMeshController.h"
#import <QuartzCore/QuartzCore.h>;
#import "NSCursor+BBCursors.h"
#import "NSView+BBViewAdditions.h"

#define BB_INNER_RADIUS 6
#define BB_OUTER_RADIUS 12

#define BB_LEFT_HANDLE 0x01
#define BB_RIGHT_HANDLE 0x02
#define BB_TOP_HANDLE 0x04
#define BB_BOTTOM_HANDLE 0x08
#define BB_MOVE_HANDLE 0x10
#define BB_ROTATE_HANDLE 0x20

@implementation BBMeshView

@synthesize mesh;
@synthesize absoluteMeshContainingRect;
@synthesize scaleX;
@synthesize scaleY;


- (id)initWithFrame:(NSRect)frame {
	self = [super initWithFrame:frame];
	if (self) {
		
		[self setupMesh];
		[[BBMeshController sharedMeshController] addObserver:self forKeyPath:@"distortionMap" options:NSKeyValueObservingOptionNew context:nil];
		// Initialization code here.
	}
	return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	[self setupMesh];
}

-(void)resetCursorRects
{
	// three options:
	// No flags: cursors on all the vertices
	// shift key down: cursors on the scale handles
	// ?? command key down: cursors on the corners for rotate
	// if we are dragging, keep the current cursor wherever we go
	if (mouseDragging) {
		[self addCursorRect:[self bounds] cursor:[NSCursor currentCursor]];
		return;
	}
	[self addCursorRect:[self bounds] cursor:[NSCursor crosshairCursor]];

	// shift key, set the scale cursors
	if (modFlags & NSShiftKeyMask) {
		for (NSValue * val in scaleRects) {
			[self addCursorRect:[val rectValue] cursor:[NSCursor arrowCursor]];
		}
		[self addCursorRect:NSInsetRect([self scaledMeshBounds], BB_OUTER_RADIUS * scaleX, BB_OUTER_RADIUS * scaleX) cursor:[NSCursor openHandCursor]];
		// four more special cursors: rotates
		NSRect bounding = [self.mesh boundingRect];

		float radius = BB_INNER_RADIUS * scaleX;
		
		NSRect lowerLeft = [self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMinY(bounding))]];
		lowerLeft.origin.x -= radius * 2.0;
		lowerLeft.origin.y -= radius * 2.0;
		
		NSRect lowerRight = [self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMinY(bounding))]];
		lowerRight.origin.x += radius * 2.0;
		lowerRight.origin.y -= radius * 2.0;
		
		NSRect upperLeft = [self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMaxY(bounding))]];
		upperLeft.origin.x -= radius * 2.0;
		upperLeft.origin.y += radius * 2.0;
		
		NSRect upperRight = [self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMaxY(bounding))]];
		upperRight.origin.x += radius * 2.0;
		upperRight.origin.y += radius * 2.0;
		
		[self addCursorRect:lowerLeft cursor:[NSCursor BBLowerLeftRotate]];
		[self addCursorRect:lowerRight cursor:[NSCursor BBLowerRightRotate]];
		[self addCursorRect:upperLeft cursor:[NSCursor BBUpperLeftRotate]];
		[self addCursorRect:upperRight cursor:[NSCursor BBUpperRightRotate]];
		return;
	}
	for (BBIndexedVertex * vert in [self.mesh vertices]) {
		NSRect cursorRect = [self handleAtPoint:[self scale:[vert point]]];
		[self addCursorRect:cursorRect cursor:[NSCursor arrowCursor]];
	}
	
}


-(void)calculateScaleRects
{
	NSRect bounding = [self.mesh boundingRect];
	if (scaleRects == nil) scaleRects = [[NSMutableArray alloc] init];
	[scaleRects removeAllObjects];
	// first, the corners
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMinY(bounding))]]]];
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMaxY(bounding))]]]];
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMinY(bounding))]]]];
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMaxY(bounding))]]]];
	// then the centers
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMidX(bounding), NSMinY(bounding))]]]];
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMidX(bounding), NSMaxY(bounding))]]]];
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMidY(bounding))]]]];
	[scaleRects addObject:[NSValue valueWithRect:[self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMidY(bounding))]]]];
}

-(NSRect)handleAtPoint:(NSPoint)p
{
	float radius = BB_INNER_RADIUS * scaleX;
	return NSMakeRect(p.x - radius, p.y - radius, radius * 2.0, radius * 2.0);	
}

-(NSRect)scaledMeshBounds
{
	NSRect bounding = [self.mesh boundingRect];
	return NSMakeRect(NSMinX(bounding) * scaleX, NSMinY(bounding) * scaleY, NSWidth(bounding) * scaleX, NSHeight(bounding) * scaleY);	
}

-(BOOL)showMesh
{
	return YES;
}

-(void)drawRect:(NSRect)rect
{
	self.scaleX = NSWidth([self bounds])/(NSWidth(absoluteMeshContainingRect));
	self.scaleY = NSHeight([self bounds])/(NSHeight(absoluteMeshContainingRect));

	if ([self showMesh]) {
		[self drawVertices];
		if (modFlags & NSShiftKeyMask) [self drawScalingRects];		
	}
	if (!isFocus) return;
	[NSGraphicsContext saveGraphicsState];
	NSSetFocusRingStyle (NSFocusRingOnly);
	NSRectFill([self bounds]);
	[NSGraphicsContext restoreGraphicsState];

}

-(void)drawScalingRects
{
	CGFloat saveWidth = [NSBezierPath defaultLineWidth];
	[NSBezierPath setDefaultLineWidth:1.5 * scaleX];
	// draw the scale handles
	[[NSColor blackColor] set];
	for (NSValue * val in scaleRects) {
		[NSBezierPath strokeRect:[val rectValue]];
	}
	[NSBezierPath strokeRect:[self scaledMeshBounds]];
	[NSBezierPath setDefaultLineWidth:saveWidth];
}

-(void)drawVertices
{
	// this draws the lines between all the vertexes to make the 'grid'
	// note: this isnt the best way to do this, it draws most lines twice
	// but it is good enough for our purposes here
	NSInteger xVerts = [self.mesh width];
	NSInteger yVerts = [self.mesh height];
		
	NSArray * vertices = [self.mesh vertices];
	NSInteger vertIndex;
	NSInteger ix,iy;
	CGFloat saveWidth = [NSBezierPath defaultLineWidth];
	[NSBezierPath setDefaultLineWidth:1.5 * scaleX];
	
	[[NSColor darkGrayColor] set];
	// first draw all the 'up' edges
	for (iy = 0; iy < yVerts - 1; iy ++) {
		for (ix = 0 ; ix < xVerts; ix++) {
			vertIndex = ix * yVerts + iy;
			// get the current layer
			BBIndexedVertex* thisVert = [vertices objectAtIndex:vertIndex];
			// draw from the current to the one above, but only if I am not on the edge already
			if ((vertIndex + 1 < [vertices count]) && (iy != yVerts - 1)) {
				BBIndexedVertex* otherVert = [vertices objectAtIndex:vertIndex + 1];
				[NSBezierPath strokeLineFromPoint:[self scale:[otherVert point]] toPoint:[self scale:[thisVert point]]];				
			}			
		}		
	}
	
	// now draw all the 'to the right' edges
	for (ix = 0 ; ix < xVerts - 1; ix++) {
		for (iy = 0; iy < yVerts; iy ++) {
			vertIndex = ix * yVerts + iy;
			// get the current layer
			BBIndexedVertex* thisVert = [vertices objectAtIndex:vertIndex];
			// draw from the current to the one above, but only if I am not on the edge already
			if (vertIndex + yVerts < [vertices count]) {
				BBIndexedVertex* otherVert = [vertices objectAtIndex:vertIndex + yVerts];
				[NSBezierPath strokeLineFromPoint:[self scale:[otherVert point]] toPoint:[self scale:[thisVert point]]];			
			}
		}		
	}
		
	// draw the vertices on top of the lines
	for (BBIndexedVertex * vert in vertices) {
		[self drawVertexAtPoint:[self scale:[vert point]]];		
	}
	
	// now draw the 'keys' so that we know which way is up and down/left and right
	// the keys are a square and a circle
	// they are in the grids adjacent to the first and last vertices (ie opposite corners)
	NSPoint squareKey = [[vertices objectAtIndex:0] point];
	NSPoint circleKey = [[vertices lastObject] point];
	
	squareKey = [self scale:squareKey];
	circleKey = [self scale:circleKey];
	float keyRadius = BB_OUTER_RADIUS * 2.0 * scaleX;
	
	[[NSBezierPath bezierPathWithRect:NSMakeRect(squareKey.x - keyRadius, squareKey.y - keyRadius, keyRadius * 2.0, keyRadius * 2.0)] stroke];
	[[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(circleKey.x - keyRadius, circleKey.y - keyRadius, keyRadius * 2.0, keyRadius * 2.0)] stroke];
	
	[NSBezierPath setDefaultLineWidth:saveWidth];
}


- (BOOL)canBecomeKeyView
{
	return YES;	
}

- (BOOL)acceptsFirstResponder
{
	return YES;	
}

- (BOOL)becomeFirstResponder
{
	// need to make a note so I can draw a focus ring
	isFocus = YES;
	[self setNeedsDisplay:YES];
	return YES;
}

- (BOOL)resignFirstResponder
{
	// need to make a note so I can draw a focus ring
	isFocus = NO;
	[self setNeedsDisplay:YES];
	return YES;
}

-(IBAction)keyDown:(NSEvent*)theEvent
{
	if ([theEvent keyCode] == 53) {
		[[NSNotificationCenter defaultCenter] postNotificationName:@"BBEscapeKeyDownNotification" object:nil];
		return;		
	}
	if ([[theEvent charactersIgnoringModifiers] isEqualToString:@"f"]) {
		[[NSNotificationCenter defaultCenter] postNotificationName:@"BBFreezeFrameNotification" object:nil];
		return;		
	}
	
	[super keyDown:theEvent];
}

// this is the parent implementation, and it just generates its own mesh
// children classes should do something better
-(void)setupMesh
{	
	self.absoluteMeshContainingRect = [[BBMeshController sharedMeshController] meshFrame];
	NSRect frame = NSInsetRect(absoluteMeshContainingRect, 120, 90);
	//	NSRect frame = NSMakeRect(0, 0, 640, 480);
	BBDistortionMesh * newMesh = [BBDistortionMesh meshWithHeight:4 width:5 boundingRectangle:frame];
	
	self.mesh = newMesh;
	[self setNeedsDisplay:YES];
}

// this is kinda ugly and terrible.  it needs to be refactored
-(void)mouseDown:(NSEvent*)theEvent
{
	if ([self isCompositeMouseDown:theEvent]) return;
	
	mousePointInView = [self convertPoint:theEvent.locationInWindow fromView:nil];

	NSPoint scaledPoint = mousePointInView;
	scaledPoint.x /= self.scaleX;	
	scaledPoint.y /= self.scaleY;	
	mouseDragging = YES;
	scaleHandle = 0;
	// check the modflags
	if (modFlags & NSShiftKeyMask) {
		NSRect bounding = [self.mesh boundingRect];

		// bottom handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMidX(bounding), NSMinY(bounding))]])) {
			scaleHandle += BB_BOTTOM_HANDLE;			
		}
		// top handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMidX(bounding), NSMaxY(bounding))]])) {
			scaleHandle += BB_TOP_HANDLE;			
		}
		// left handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMidY(bounding))]])) {
			scaleHandle += BB_LEFT_HANDLE;			
		}
		// right handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMidY(bounding))]])) {
			scaleHandle += BB_RIGHT_HANDLE;			
		}

		// lower left handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMinY(bounding))]])) {
			scaleHandle += BB_LEFT_HANDLE + BB_BOTTOM_HANDLE;			
		}
		// lower right handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMinY(bounding))]])) {
			scaleHandle += BB_RIGHT_HANDLE + BB_BOTTOM_HANDLE;			
		}

		// upper right handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding), NSMaxY(bounding))]])) {
			scaleHandle += BB_RIGHT_HANDLE + BB_TOP_HANDLE;			
		}

		// upper left handle
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding), NSMaxY(bounding))]])) {
			scaleHandle += BB_LEFT_HANDLE + BB_TOP_HANDLE;			
		}
		
		// moving handle
		if (NSPointInRect(mousePointInView, NSInsetRect([self scaledMeshBounds], BB_OUTER_RADIUS, BB_OUTER_RADIUS))) {
			scaleHandle = BB_RIGHT_HANDLE + BB_LEFT_HANDLE + BB_TOP_HANDLE + BB_BOTTOM_HANDLE;			
		}
		
		// rotates
		//lower left
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding) - BB_INNER_RADIUS * 2.0, NSMinY(bounding) - BB_INNER_RADIUS * 2.0)]])) {
			scaleHandle = BB_ROTATE_HANDLE;
		}
		
		// lower right
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding) + BB_INNER_RADIUS * 2.0, NSMinY(bounding) - BB_INNER_RADIUS * 2.0)]])) {
			scaleHandle = BB_ROTATE_HANDLE;			
		}
		// upper left
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMinX(bounding) - BB_INNER_RADIUS * 2.0, NSMaxY(bounding) + BB_INNER_RADIUS * 2.0)]])) {
			scaleHandle = BB_ROTATE_HANDLE;
		}
		// upper right
		if (NSPointInRect(mousePointInView, [self handleAtPoint:[self scale:NSMakePoint(NSMaxX(bounding) + BB_INNER_RADIUS * 2.0, NSMaxY(bounding) + BB_INNER_RADIUS * 2.0)]])) {
			scaleHandle = BB_ROTATE_HANDLE;
		}		
		
		[[self window] invalidateCursorRectsForView:self];	
		[self setNeedsDisplay:YES];
		return;
	} 
	// find the vertex that we hit
	// scale the mouse point to the absolute size to match the vertexes
	draggingVertex = [self vertextAtPoint:mousePointInView];
	[[self window] invalidateCursorRectsForView:self];	
	[self setNeedsDisplay:YES];
}

-(void)mouseUp:(NSEvent*)theEvent
{
	mousePointInView = [self convertPoint:theEvent.locationInWindow fromView:nil];
	mouseDragging = NO;
	[[self window] invalidateCursorRectsForView:self];
	[self setNeedsDisplay:YES];
}

-(void)mouseDragged:(NSEvent*)theEvent
{
	lastMousePointInView = mousePointInView;
	mousePointInView = [self convertPoint:theEvent.locationInWindow fromView:nil];
	mouseDragging = YES;

	// are we rotating? then rotate
	if (scaleHandle & BB_ROTATE_HANDLE) {
		[self dragRotate:theEvent];
		[self setNeedsDisplay:YES];
		return;		
	}
	
	// are we scaling? (or moving) then scale (or move)
	if (scaleHandle > 0) {
		// then we are scaling
		[self dragScale:theEvent];
		[self setNeedsDisplay:YES];
		return;
	}
	
	// neither scaling or rotating or moving, then we are dragging vertices about
	
	if (draggingVertex != nil) {
		// we are inside one of the vertext layers, so move it
		NSPoint position = [draggingVertex point];
		position.x += [theEvent deltaX] / self.scaleX;
		position.y -= [theEvent deltaY] / self.scaleY;
		
		[draggingVertex setPoint:position];
		[self.mesh calculateBoundingRect];
		[self setNeedsDisplay:YES];
	} 
}

-(void)dragRotate:(NSEvent *)theEvent
{
	// need to rotate the mesh
	NSPoint center = NSMakePoint(NSMidX([self scaledMeshBounds]), NSMidY([self scaledMeshBounds]));	

	// make a normalized vector from the center to the last mouse point
	double magnitude = hypot(center.x - lastMousePointInView.x,center.y - lastMousePointInView.y);
	NSPoint vec1 = NSMakePoint(((center.x - lastMousePointInView.x)/magnitude),((center.y - lastMousePointInView.y)/magnitude));	

	// make a normalized vector from teh center to this mouse point
	magnitude = hypot(center.x - mousePointInView.x,center.y - mousePointInView.y);
	NSPoint vec2 = NSMakePoint(((center.x - mousePointInView.x)/magnitude),((center.y - mousePointInView.y)/magnitude));	
	
	
	float dot =  (vec1.x * vec2.x) + (vec1.y * vec2.y);
	// need to sign it properly
	double slope1 = vec1.y/vec1.x;
	double slope2 = vec2.y/vec2.x;
	int sign = 1.0;
	if (slope2 < slope1) {
		sign = -1.0;
	}
	double theta = acos(dot) * sign; // in radians
	
	[self.mesh rotateMeshInRadians:theta];	
	[self calculateScaleRects];
}

-(void)dragScale:(NSEvent *)theEvent
{
	NSRect bounding = [self.mesh boundingRect];
	float maxX = NSMaxX(bounding);
	float maxY = NSMaxY(bounding);
	float minY = NSMinY(bounding);
	float minX = NSMinX(bounding);

	float xInc = [theEvent deltaX] / self.scaleX;
	float yInc = [theEvent deltaY] / self.scaleY;	
	
	if (scaleHandle & BB_LEFT_HANDLE) {
		minX += xInc;	
	}
	if (scaleHandle & BB_RIGHT_HANDLE) {
		maxX += xInc;	
	}
	if (scaleHandle & BB_TOP_HANDLE) {
		maxY -= yInc;	
	}
	if (scaleHandle & BB_BOTTOM_HANDLE) {
		minY -= yInc;	
	}
	if (scaleHandle & BB_MOVE_HANDLE) {
	}
	
	NSRect newBounding = NSMakeRect(minX, minY, maxX - minX, maxY - minY);

	[self.mesh scaleBoundingRectToNewRect:newBounding];	
	[self calculateScaleRects];
}


- (void)flagsChanged:(NSEvent *)theEvent
{
	modFlags = [theEvent modifierFlags];
	[self calculateScaleRects];
	[[self window] invalidateCursorRectsForView:self];
	[self setNeedsDisplay:YES];
}

// this does a hit test on all the vertices to see if we hit one
-(BBIndexedVertex*)vertextAtPoint:(NSPoint)p
{
	for (BBIndexedVertex* vert in [self.mesh vertices]) {
		NSPoint vPoint = [self scale:[vert point]];
		//TODO: go away magic numbers!
		if (NSPointInRect(p, NSMakeRect(vPoint.x - BB_OUTER_RADIUS, vPoint.y - BB_OUTER_RADIUS,BB_OUTER_RADIUS * 2,BB_OUTER_RADIUS * 2))) {
			return vert;
		}
	}
	return nil;	
}



-(void)drawVertexAtPoint:(NSPoint)p
{
	[[NSColor blackColor] set];
	float radius = BB_OUTER_RADIUS * scaleX;
	
	[NSBezierPath strokeLineFromPoint:NSMakePoint(p.x - radius, p.y) toPoint:NSMakePoint(p.x + radius, p.y)];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(p.x, p.y - radius) toPoint:NSMakePoint(p.x, p.y + radius)];
	if (modFlags & NSShiftKeyMask) return; //dont draw the target circle

	NSRect targetRect = [self handleAtPoint:p];
	[[NSBezierPath bezierPathWithOvalInRect:targetRect] stroke];
	
}

-(NSPoint)scale:(NSPoint)p
{
	return NSMakePoint(p.x * scaleX, p.y * scaleY);
}

@end
